package Executor;

import Executor.entity.User;
import org.apache.ibatis.annotations.Select;
import org.testcontainers.shaded.com.google.common.collect.Maps;

import java.lang.reflect.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

interface UserMapper {
  @Select("SELECT * FROM mybatis.user WHERE id = #{id}")
  List<User> selectUserList(Integer id);
}

public class JDKMybatis {
  public static void main(String[] args) {
    UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(
      JDKMybatis.class.getClassLoader(),
      new Class<?>[]{UserMapper.class},
      new InvocationHandler() {
        /**
         * 在 invoke() 方法中就可以进行
         * @param proxy  动态代理
         * @param method 方法
         * @param args   参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          // 把注解类获取，可以查出注解的值等多种其他值
          Select annotation = method.getAnnotation(Select.class);
          Map<String, Object> argsNameMap = MapBuildArgsName(method, args);
          if (annotation != null) {
            String[] value = annotation.value();
            String sql = value[0];
            sql = parseSql(sql, argsNameMap);
            // 方法返回类型
            Class<?> returnType = method.getReturnType();
            // 方法返回的泛型类
            Type genericReturnType = method.getGenericReturnType();
            ArrayList resultSet = dataConnection(sql, returnType, genericReturnType);
            System.out.println(resultSet);
          }
          // TODO: 通过 method 反射拿到结果返回的类型，再把查出来的数据放进一个结果集，再 Return
          return null;
        }
      });

    userMapper.selectUserList(1);
  }

  private static ArrayList dataConnection(String sql, Class<?> returnType, Type genericReturnType) {
    Connection connection = null;
    Statement statement = null;
    ResultSet resultSet = null;
    Class<?> demo = null;

    // TODO: 将返回类型取出来使用，进行转换得出类型再返回出去
    ArrayList list = null;
    try {
      Class.forName("com.mysql.jdbc.Driver");
      connection = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT",
        "root",
        "123456");
      statement = connection.createStatement();
      resultSet = statement.executeQuery(sql);

      demo = Class.forName(returnType.getTypeName());
      list = new ArrayList<>();
      while (resultSet.next()) {
        int id = resultSet.getInt(1);
        String username = resultSet.getString(2);
        User user = new User();
        user.setId(id);
        user.setUsername(username);

        Field[] declaredFields = demo.getDeclaredFields();

        list.add(user);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (resultSet != null) {
        try {
          resultSet.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (statement != null) {
        try {
          statement.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (connection != null) {
        try {
          connection.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
    return list;
  }

  private static String parseSql(String sql, Map<String, Object> argsNameMap) {
    // 定义为常量数组
    char[] str = {'#', '{'};
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < sql.length(); i++) {
      char aloneParseSql = sql.charAt(i);

      if (str[0] == aloneParseSql) {
        int nextIndex = i + 1;
        char nextChar = sql.charAt(nextIndex);

        // # 后应该是 { ，不匹配直接抛出异常
        if (str[1] != nextChar) {
          throw new RuntimeException(String.format(
            "此处应该是：#{\n sql：%s\n index：%d",
            stringBuilder.toString(), nextIndex));
        }
        /*
          1 已经解析完的下标
          2 解析完的 #{} 内的参数名
          3 把对应的 argsName 的值 argsValue 取出来
          4 追加到原来的 stringBuilder 中的 sql 语句后面
         */
        StringBuilder partStringBuilder = new StringBuilder();
        i = partParseSql(partStringBuilder, sql, nextIndex);
        String argsName = partStringBuilder.toString();
        Object argsValue = argsNameMap.get(argsName);
        if (argsValue == null) {
          throw new NullPointerException(String.format(
            "\n没有参数值：\nvalue：%s",
            argsValue));
        }
        stringBuilder.append(argsValue.toString());
      }

      // 如果没有条件，直接追加
      stringBuilder.append(aloneParseSql);
    }
    return stringBuilder.toString();
  }

  private static int partParseSql(StringBuilder partStringBuilder, String sql, int nextIndex) {
    // 由于 nextIndex 当前指针指向的是 { 所以要加一位，把后面内容解析
    nextIndex++;
    char[] str = {'}'};
    for (; nextIndex < sql.length(); nextIndex++) {
      char indexSql = sql.charAt(nextIndex);
      if (str[0] != indexSql) {
        partStringBuilder.append(indexSql);
      }
      if (str[0] == indexSql) {
        return nextIndex;
      }
    }
    throw new RuntimeException(String.format(
      "缺少：}\n index：%d",
      nextIndex));
  }

  /**
   * 1 创建一个新的 Map 存放参数解析后的值
   * 2 先从 method 中把 parameter 获取出来
   * 3 遍历 parameter 把名字取出来 就是解析的 args 值
   *
   * @param method 方法体
   * @param args   参数
   * @return 返回参数解析
   */
  private static Map<String, Object> MapBuildArgsName(Method method, Object[] args) {
    Map<String, Object> argsNameMap = Maps.newHashMap();
    Parameter[] parameters = method.getParameters();
    final int[] index = {0};
    Arrays.asList(parameters)
      .forEach(parameter -> {
        String argsName = parameter.getName();
        argsNameMap.put(argsName, args[index[0]]);
        index[0]++;
      });
    return argsNameMap;
  }
}
